/********************************************************************************************************
 * @file OtaActivity.java
 *
 * @brief for TLSR chips
 *
 * @author telink
 * @date Sep. 30, 2019
 *
 * @par Copyright (c) 2019, Telink Semiconductor (Shanghai) Co., Ltd. ("TELINK")
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.telink.lt.ui

import android.annotation.SuppressLint
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattService
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.Handler
import android.os.Message
import android.text.Html
import android.util.Log
import android.view.View
import android.widget.*
import androidx.appcompat.widget.Toolbar
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView

import com.telink.lt.ui.BaseActivity
import com.telink.ota.foundation.OtaSetting
import com.telink.ota.ble.GattConnection
import com.telink.ota.ble.OtaController
import com.telink.lt.ui.OtaActivity
import com.telink.lt.adapter.FileAdapter
import com.telink.lt.OtaSettingService
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import com.telink.lt.ServiceApi
import com.telink.lt.ui.otaBean
import com.chad.library.adapter.base.BaseQuickAdapter
import com.kongzue.dialogx.dialogs.TipDialog
import com.kongzue.dialogx.dialogs.WaitDialog
import com.telink.lt.R
import com.telink.lt.TelinkLog
import com.telink.ota.foundation.OtaProtocol
import com.telink.lt.ui.OtaSettingActivity
import com.telink.lt.ui.file.FileSelectActivity
import com.telink.ota.ble.Command
import com.telink.ota.ble.OtaController.GattOtaCallback
import com.telink.ota.foundation.Opcode
import com.telink.ota.foundation.OtaStatusCode
import com.telink.ota.util.Arrays
import kotlinx.coroutines.launch
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import java.io.File
import java.lang.StringBuilder
import java.util.*
import kotlin.experimental.xor

/**
 * Device OTA
 * connect --> discoverService --> display
 */
class OtaActivity : BaseActivity(), View.OnClickListener {
    private var device: BluetoothDevice? = null

    /**
     * the device that run the
     */
    private var bootloaderDevice: BluetoothDevice? = null
    private var selectFile: TextView? = null
    private var tv_device_info: TextView? = null
    private var tv_connection: TextView? = null
    private var tv_progress: TextView? = null
    private var tv_info: TextView? = null
    private var tv_fw_info: TextView? = null
    private var iv_settings: ImageView? = null
    private var iv_visible: ImageView? = null
    private var tv_setting_info: TextView? = null
    private var tv_mtu: TextView? = null
    private var btn_ota: Button? = null
    private var btn_connect_action: Button? = null
    private var pb_connecting: ProgressBar? = null
    private var pb_ota: ProgressBar? = null
    var clickData: String? = null
    /**
     * is the gatt connected
     */
    var isConnected = false
    private var otaRunning = false
    private var otaSetting: OtaSetting? = null
    private var mGattConnection: GattConnection? = null
    private var mOtaController: OtaController? = null

    /**
     * control the setting layout visibility
     */
    private var isSettingVisible = true
    private var blState = BL_OTA_STATE_IDLE
    var adapter = FileAdapter()

    @SuppressLint("HandlerLeak")
    private val mInfoHandler: Handler = object : Handler() {
        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
            when (msg.what) {
                MSG_PROGRESS -> {
                    tv_progress!!.text = msg.obj.toString() + "%"
                    pb_ota!!.progress = (msg.obj as Int)
                    WaitDialog.show("正在升级${pb_ota!!.progress}%",  pb_ota!!.progress / 100f)

                }
                MSG_INFO -> {
                    val code = msg.arg1
                    val info = msg.obj as String
                    if ("OTA success".equals(info)){
//                        TipDialog.show(info)
                        TipDialog.show(info,WaitDialog.TYPE.SUCCESS)
                    }else if ("OTA started" == info){
//                        TipDialog.show(info)
                    }else{
                        TipDialog.show(info,WaitDialog.TYPE.ERROR)
                    }
                    Log.e(TAG, "handleMessage: " + info )
                    tv_info!!.append("\n"+info)

                }
                MSG_CONNECTION -> {
                    val state = msg.arg1
                    val statusCode = msg.arg2 // 133
                    /*if (msg.arg1 == BluetoothGatt.STATE_CONNECTING) {
                            pb_connecting.setVisibility(View.VISIBLE);
                        } else {
                            pb_connecting.setVisibility(View.GONE);
                        }*/
                    btn_connect_action!!.text =
                        if (state == BluetoothGatt.STATE_CONNECTED) "disconnect" else "connect"
                    getConnectionDesc(state)
//                    tv_connection!!.text = getConnectionDesc(state)
//                    WaitDialog.show(tv_connection!!.text)
                }
                MSG_MTU_UPDATE -> {
                    val mtu = msg.obj as Int
                    tv_mtu!!.text = getString(R.string.mtu_desc, mtu)
                }
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_ota)
        val intent = intent
        device = intent.getParcelableExtra("device")
        if (device == null) {
            finish()
            Toast.makeText(applicationContext, "device null", Toast.LENGTH_SHORT).show()
            return
        }
        getBootloaderDevice()
        initViews()
        enableBackNav(true)
        setTitle("OTA")
        OtaSettingService.getInstance().load(this)
        updateSettingInfo()
        mGattConnection = GattConnection(this)
        mGattConnection!!.setConnectionCallback(CONN_CB)
        mGattConnection!!.connect(device)
        mOtaController = OtaController(mGattConnection)
        mOtaController!!.setOtaCallback(OTA_CB)
        val rvFile = findViewById<RecyclerView>(R.id.rvFile)
        val layoutManager = LinearLayoutManager(this)
        rvFile.layoutManager = layoutManager
        val itemDecoration = DividerItemDecoration(
            this,
            LinearLayoutManager.VERTICAL
        )
        rvFile.addItemDecoration(itemDecoration)
        rvFile.adapter = adapter
        val build = Retrofit.Builder().baseUrl("https://www.bianjiaojing.com/")
            .addConverterFactory(GsonConverterFactory.create()).build()
        val serviceApi = build.create(ServiceApi::class.java)
        serviceApi[5].enqueue(object : Callback<otaBean?> {
            override fun onResponse(call: Call<otaBean?>, response: Response<otaBean?>) {
                if (response.isSuccessful) {
                    val body = response.body()
                    val data = body!!.data
                    adapter.setList(data)
                    val toString = data.toString()
                    Log.e("TAG", "onResponse: $toString")
                }
            }

            override fun onFailure(call: Call<otaBean?>, t: Throwable) {
                Toast.makeText(this@OtaActivity, t.message, Toast.LENGTH_LONG).show()
            }
        })
        adapter.setOnItemClickListener { adapter, view, position ->
            clickData = (adapter.data[position] as Data).adUrl
            title = (adapter.data[position] as Data).adTitle
            download(this@OtaActivity, clickData)
        }
    }

    private fun download(otaActivity: OtaActivity, clickData: String?) {
        val time = System.currentTimeMillis()
        //        LogUtil.d("time1:${time}");
        val file2 = getTargetParentFile(otaActivity)
        lifecycleScope.launch {
            DownloadGO.download(
                url = clickData!!,
                targetParent = file2.path
            ).collect {
                when (it) {
                    is DownloadStatus.onFail -> {

                        Toast.makeText(this@OtaActivity, "下载失败"+  it.error, Toast.LENGTH_LONG).show()
                    }
                    is DownloadStatus.onProgress -> {
                        val pb = (it.progress?.div(100f))
                        WaitDialog.show("正在下载" + (it.progress) + "%", pb!!.toFloat())

                    }
                    is DownloadStatus.onSuccess -> {
                        Log.e("TAG", "downloadSuccess: ")

                        TipDialog.show("下载完成", WaitDialog.TYPE.SUCCESS, 500)

                        otaSetting!!.firmwarePath = it.file?.absolutePath

                        otaRunning = true
                        updateUI()
                        tv_info!!.text = "start OTA"
                        tv_progress!!.text = ""
                        pb_ota!!.progress = 0
                        if (otaSetting!!.bootloaderModeEnable) {
                            if (mGattConnection!!.isConnected) {
                                sendFirstStartCommand()
                            } else {
                                appendLog("bootloader: device not connected")
                                otaRunning = false
                                updateUI()
                            }
                        } else {
                            mOtaController!!.startOta(otaSetting)
                        }
//                        val intent = Intent()
//                        Log.e("TAG", "absolutePath: "+ it.file?.absolutePath)
//                        intent.putExtra(FileSelectActivity.KEY_RESULT,it.file?.absolutePath)
//                        intent.putExtra("title",title)
//                        setResult(RESULT_OK, intent)
//                        finish()

                    }
                    else -> {

                    }
                }
            }

            LogUtil.d("处理完成")
        }
    }

    private fun getTargetParentFile(context: Context): File {
        val file0 = context.externalCacheDir.toString()
        val file1 = File("$file0/MyDownload")
        if (!file1.exists() || !file1.isDirectory) {
            file1.mkdirs()
        }
        return file1
    }


    private fun getBootloaderDevice() {
        // D1 08 B0 DA D4 B4 - origin device
        // C1 22 B0 DA D4 B5 - bootloader device
        val origin = device!!.address
        val mac = Arrays.hexToBytes(origin.replace(":", "")) // convert mac string to bytes
        mac[0] = 0xC1.toByte()
        mac[1] = 0x22.toByte()
        // 2, 3, 4 remain the same
        mac[5] = mac[5] xor 1
        bootloaderDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(mac)
        TelinkLog.d("bootloaderDevice - " + bootloaderDevice!!.getAddress())
    }

    private fun initViews() {
        val toolbar = findViewById<Toolbar>(R.id.title_bar)
//        toolbar.inflateMenu(R.menu.menu_ota)
//        toolbar.setOnMenuItemClickListener { item ->
//            if (item.itemId == R.id.item_refresh_cache) {
//                if (mGattConnection != null) {
//                    mGattConnection!!.refreshCache()
//                }
//            }
//            false
//        }
        selectFile = findViewById(R.id.selectFile)
        tv_info = findViewById(R.id.info)
        tv_progress = findViewById(R.id.progress)
        tv_mtu = findViewById(R.id.tv_mtu)
        btn_ota = findViewById(R.id.btn_ota)
        iv_settings = findViewById(R.id.iv_settings)
        iv_visible = findViewById(R.id.iv_visible)
        tv_setting_info = findViewById(R.id.tv_setting_info)
        btn_connect_action = findViewById(R.id.btn_connect_action)
        tv_connection = findViewById(R.id.tv_connection)
        tv_device_info = findViewById(R.id.tv_device_info)
        tv_fw_info = findViewById(R.id.tv_fw_info)
        val name = if (device!!.name == null || device!!.name == "") "Unknown" else device!!.name
        tv_device_info?.setText(name + "(" + device!!.address + ")")
        selectFile?.setOnClickListener(this)
        btn_ota?.setOnClickListener(this)
        btn_connect_action?.setOnClickListener(this)
        pb_connecting = findViewById(R.id.pb_connecting)
        pb_ota = findViewById(R.id.pb_ota)
        iv_settings?.setOnClickListener(this)
        iv_visible?.setOnClickListener(this)
    }

    private fun updateSettingInfo() {
        otaSetting = OtaSettingService.getInstance().get()
        val info = StringBuilder()
        appendTitle(info, "\tservice: ")
        if (otaSetting?.serviceUUID != null) {
            appendContent(info, otaSetting?.serviceUUID.toString())
        } else {
            appendContent(info, "[use default(1912)]")
        }
        appendTitle(info, "\n\tcharacteristic: ")
        if (otaSetting?.characteristicUUID != null) {
            appendContent(info, otaSetting?.characteristicUUID.toString())
        } else {
            appendContent(info, "[use default(2B12)]")
        }
        appendTitle(info, "\n\tread interval: ")
        appendContent(info, otaSetting?.readInterval.toString() + "")
        appendTitle(info, "\n\tbin path: ")
        if (otaSetting?.firmwarePath != null) {
            appendContent(info, otaSetting?.firmwarePath!!)
        } else {
            appendContent(info, "error - file not selected")
        }

        // secure boot
        appendTitle(info, "\n\tsecure boot: ")
        appendContent(info, otaSetting?.secureBootEnable.toString() + "")
        if (otaSetting?.secureBootEnable == true) {
            appendTitle(info, "\n\tRun Code Description file: ")
            appendContent(info, otaSetting?.descriptorPath!!)
        }
        appendTitle(info, "\n\tsend firmware index: ")
        appendContent(info, otaSetting?.sendFwIndex.toString() + "")
        if (otaSetting?.sendFwIndex == true) {
            appendTitle(info, "\n\t\tfirmware index: ")
            appendContent(info, String.format("%02X", otaSetting?.fwIndex))
        }
        appendTitle(info, "\n\tbootloader mode: ")
        appendContent(info, otaSetting?.bootloaderModeEnable.toString() + "")
        val isLegacyProtocol = otaSetting?.protocol == OtaProtocol.Legacy
        appendTitle(info, "\n\tprotocol: ")
        appendContent(info, if (isLegacyProtocol) "Legacy" else "Extend")
        if (!isLegacyProtocol) {
            appendTitle(info, "\n\tversion compare: ")
            appendContent(info, otaSetting?.versionCompare.toString() + "")
            appendTitle(info, "\n\tbin version: ")
            appendContent(info, Arrays.bytesToHexString(otaSetting?.firmwareVersion, ":"))
            appendTitle(info, "\n\tpdu length: ")
            appendContent(info, otaSetting?.pduLength.toString() + "")
        }
        tv_setting_info!!.text = Html.fromHtml(info.toString())
        //        tv_setting_info.setText(info.toString());
    }

    private fun appendTitle(info: StringBuilder, title: String): StringBuilder {
        info.append(title)
        return info
    }

    private fun appendContent(info: StringBuilder, content: String): StringBuilder {
        info.append("<font color = '#0E1E59'>").append(content).append("</font><br>") // <br>
        return info
    }

    /**
     * append log to tv_log
     */
    private fun appendLog(log: String) {
        mInfoHandler.obtainMessage(MSG_INFO, log).sendToTarget()
    }

    override fun onDestroy() {
        super.onDestroy()
        if (mGattConnection != null) {
            mGattConnection!!.clearAll(true)
        }
    }

    private fun updateUI() {
        btn_ota!!.isEnabled = !otaRunning
        iv_settings!!.isEnabled = !otaRunning
        selectFile!!.isEnabled = !otaRunning
    }

    override fun onClick(v: View) {
        when (v.id) {
            R.id.btn_ota -> {
                otaRunning = true
                updateUI()
                tv_info!!.text = "start OTA"
                tv_progress!!.text = ""
                pb_ota!!.progress = 0
                if (otaSetting!!.bootloaderModeEnable) {
                    if (mGattConnection!!.isConnected) {
                        sendFirstStartCommand()
                    } else {
                        appendLog("bootloader: device not connected")
                        otaRunning = false
                        updateUI()
                    }
                } else {
                    mOtaController!!.startOta(otaSetting)
                }
            }
            R.id.btn_connect_action -> if (!isConnected) {
                mGattConnection!!.connect(device)
            } else {
                mGattConnection!!.disconnect()
            }
            R.id.iv_settings -> startActivityForResult(
                Intent(this, OtaSettingActivity::class.java).putParcelableArrayListExtra(
                    "services",
                    mGattConnection!!.services as ArrayList<BluetoothGattService?>
                ),
                REQUEST_CODE_OTA_SETTING
            )
            R.id.iv_visible -> {
                isSettingVisible = !isSettingVisible
                tv_setting_info!!.visibility =
                    if (isSettingVisible) View.VISIBLE else View.GONE
                iv_visible!!.setImageResource(if (isSettingVisible) R.drawable.ic_eye_off else R.drawable.ic_eye_on)
            }
        }
    }

    /**
     * send ota start command and wait for disconnection callback
     */
    private fun sendFirstStartCommand() {
        updateBlState(
            BL_OTA_STATE_SEND_FIRST_START,
            "send first start command -> waiting for disconnection callback"
        )
        val cmd = Command.newInstance()
        cmd.serviceUUID = otaSetting!!.otaService
        cmd.characteristicUUID = otaSetting!!.otaCharacteristic
        cmd.type = Command.CommandType.WRITE_NO_RESPONSE
        val startOp = Opcode.CMD_OTA_START.value
        cmd.data = byteArrayOf((startOp and 0xFF).toByte(), (startOp shr 8 and 0xFF).toByte())
        mGattConnection!!.sendCommand(null, cmd)
    }

    /**
     * is bootloader mode enable
     */
    private val isBlMode: Boolean
        private get() = otaSetting!!.bootloaderModeEnable
    private val isBlWaitingForDisconnect: Boolean
        private get() = isBlMode && blState == BL_OTA_STATE_SEND_FIRST_START
    private val isBlWaitingForStart: Boolean
        private get() = isBlMode && blState == BL_OTA_STATE_RECONNECT

    /**
     * reconnect bootloader device
     */
    private fun connectBlDevice() {
        updateBlState(
            BL_OTA_STATE_RECONNECT,
            "connect bootloader device - " + bootloaderDevice!!.address
        )
        mGattConnection!!.connect(bootloaderDevice)
        /*mInfoHandler.postDelayed(new Runnable() {
            @Override
            public void run() {

            }
        }, 1500);*/
    }

    private fun startBlOta() {
        updateBlState(BL_OTA_STATE_START, "start OTA")
        mInfoHandler.postDelayed({ mOtaController!!.startOta(otaSetting) }, 500)
    }

    private fun updateBlState(state: Int, desc: String) {
        blState = state
        appendLog("bootloader: $desc")
    }

    public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (resultCode != RESULT_OK) return
        if (requestCode == REQUEST_CODE_OTA_SETTING) {
            updateSettingInfo()
        }
    }

    val CONN_CB: GattConnection.ConnectionCallback = object : GattConnection.ConnectionCallback {
        override fun onConnectionStateChange(
            state: Int,
            gattConnection: GattConnection,
            statusCode: Int
        ) {
            isConnected = state == BluetoothGatt.STATE_CONNECTED
            val isDisconnected = state == BluetoothGatt.STATE_DISCONNECTED
            mInfoHandler.obtainMessage(MSG_CONNECTION, state, statusCode).sendToTarget()
            if (otaRunning) {
                if (isConnected) {
                    if (isBlWaitingForStart) {
                        startBlOta()
                    }
                } else if (isDisconnected) {
                    appendLog(String.format("device disconnected when ota: 0x%02X", statusCode))
                    if (isBlWaitingForDisconnect) {
                        connectBlDevice()
                    } else {
                        mOtaController!!.stopOta(false)
                        otaRunning = false
                    }
                }
            }
        }

        override fun onNotify(
            data: ByteArray,
            serviceUUID: UUID,
            characteristicUUID: UUID,
            connection: GattConnection
        ) {
            if (mOtaController != null) {
                mOtaController!!.pushNotification(data)
            }
        }

        override fun onMtuChanged(mtu: Int, connection: GattConnection) {
            mInfoHandler.obtainMessage(MSG_MTU_UPDATE, mtu).sendToTarget()
        }
    }
    val OTA_CB: GattOtaCallback = object : GattOtaCallback {
        override fun onOtaStatusChanged(
            code: Int,
            info: String,
            connection: GattConnection,
            controller: OtaController
        ) {
            appendLog(info)
            runOnUiThread {
                if (OtaStatusCode.isComplete(code)) {
                    otaRunning = false
                    updateUI()
                }
                if (code == OtaStatusCode.SUCCESS) {
                    Toast.makeText(this@OtaActivity, info, Toast.LENGTH_SHORT).show()
                }
            }
        }

        override fun onOtaProgressUpdate(
            progress: Int,
            connection: GattConnection,
            controller: OtaController
        ) {
            mInfoHandler.obtainMessage(MSG_PROGRESS, progress).sendToTarget()
        }
    }

    fun getConnectionDesc(connectionState: Int) {
         when (connectionState) {
//            BluetoothGatt.STATE_DISCONNECTED -> TipDialog.show("已断开连接")
            BluetoothGatt.STATE_CONNECTING -> WaitDialog.show("连接中...")
            BluetoothGatt.STATE_CONNECTED -> TipDialog.show("已连接",WaitDialog.TYPE.SUCCESS)
//            BluetoothGatt.STATE_DISCONNECTING -> TipDialog.show("断开连接中...")
            else -> "unknown"
        }
    }

    companion object {
        private const val REQUEST_CODE_OTA_SETTING = 1
        private const val MSG_PROGRESS = 11
        private const val MSG_INFO = 12
        private const val MSG_CONNECTION = 13
        private const val MSG_MTU_UPDATE = 14

        /**
         * idle
         */
        private const val BL_OTA_STATE_IDLE = 0x00

        /**
         * send first start command complete -> waiting for disconnection callback
         */
        private const val BL_OTA_STATE_SEND_FIRST_START = 0x01

        /**
         * original device disconnected -> connect the bootloader device [.bootloaderDevice]
         */
        private const val BL_OTA_STATE_RECONNECT = 0x02

        /**
         * connect  bootloader device [.bootloaderDevice] connect
         */
        private const val BL_OTA_STATE_START = 0x03
    }
}